/*
 * $Id: Daemon.cc,v 1.3 2006-02-07 06:02:35 bacon Exp $
 */

#include <xpp/sys/Daemon.h>
#include <xp/bas/sysapi.h>
#include <xp/bas/stdlib.h>

namespace xpp
{
	namespace sys
	{
		
		Daemon::Daemon (bool root_only)
		{
			this->root_only   = root_only;
			this->return_code = 0;
		}

		Daemon::Daemon (const xp_char_t* name, bool root_only): Named (name)
		{
			this->root_only   = root_only;
			this->return_code = 0;
		}

		Daemon::~Daemon ()
		{
		}
		
		int Daemon::start (int* err_code)
		{ 
			if (err_code != XP_NULL) *err_code = ERR_NONE;
		
		#ifdef _WIN32
			SERVICE_TABLE_ENTRY service_table[] = {
				{(LPTSTR)this->name(), (LPSERVICE_MAIN_FUNCTION)Daemon::service_main},
				{XP_NULL, XP_NULL}
			};

			if (::StartServiceCtrlDispatcher (service_table) == FALSE) {
				if (err_code != XP_NULL) *err_code = ERR_SCD;
				return -1;
			}
		#else
			if (root_only && ::xp_getuid() != 0) {
				//throw DaemonError ("not a root user");
				if (err_code != XP_NULL) *err_code = ERR_NON_ROOT;
				return -1;
			}
		
			::xp_umask (0);
			if (chdir ("/") == -1) {
				//throw DaemonError ("can't chdir to /");
				if (err_code != XP_NULL) *err_code = ERR_CHDIR;
				return -1;
			}
		
			int n = ::xp_fork ();
			if (n == -1) {
				//throw DaemonError ("can't xp_fork");
				if (err_code != XP_NULL) *err_code = ERR_FORK;
				return -1;
			}
		
			// the parent exits
			if (n != 0) xp_exit (0); 
		
			// for (int i = NOFILE - 1; i >= 0; i--) xp_close (i);
		
			// the child process becomes a daemon
			if (::xp_setsid () == -1) {
				if (err_code != XP_NULL) *err_code = ERR_SETSID;
				return -1;
			}
			
			return_code = run ();
		#endif
			return 0;
		}

		int Daemon::startAsNonDaemon (int* err_code)
		{
			if (err_code != XP_NULL) *err_code = ERR_NONE;
		
		#ifndef _WIN32
			if (root_only && ::xp_getuid () != 0) {
				if (err_code != XP_NULL) *err_code = ERR_NON_ROOT;
				return -1;
			}
		#endif
		
			return_code = run ();
			return 0;
		}

		#ifdef _WIN32
		void WINAPI Daemon::service_main (DWORD argc, LPTSTR* argv)
		{
			// service_main should immediately call RegisterServiceCtrlHandler 
			// to specify a function to deal with service control requests. 
			// Then it should call SetServiceStatus to report status 
			// information. It initializes the service and wait for the 
			// termination of the service while working. When it terminates,
			// it reports to the SCM the service has stopped.
			Daemon* self = (Daemon*)instance;
		
			self->status_handle = 
				::RegisterServiceCtrlHandler ((LPTSTR)self->name(),
					(LPHANDLER_FUNCTION)Daemon::control_handler);
			//if (!self->status_handle) 
			//	throw DaemonError ("can't register a service control handler");
			if (!self->status_handle) return;
		
			// when the status report succeeds, 
			// call the user-level init and main of the service
			if (self->report_action_start (
					SERVICE_START_PENDING, 
					self->wait_hint.startHint())) {
				//self->init ();
				if (self->report_action_complete (SERVICE_RUNNING)) 
					self->return_code = self->run ();
			}
		
			// don't care whether it fails or not at this phase
			self->report_action_complete  (SERVICE_STOPPED);
		}
		
		void WINAPI Daemon::control_handler (DWORD control_code)
		{
			// just in case an instance of Daemon has been 
			// destroyed but this fucntion is called.
			if (instance == XP_NULL) return;
		
			Daemon* self = (Daemon*)instance;

			switch (control_code) {
			case SERVICE_CONTROL_STOP:
				self->report_action_start  (
					SERVICE_STOP_PENDING, self->wait_hint.stopHint());
				self->stop ();
				break;
			case SERVICE_CONTROL_SHUTDOWN:
				self->report_action_start (
					SERVICE_STOP_PENDING, self->wait_hint.stopHint());
				self->shutdown ();
				break;
			case SERVICE_CONTROL_PAUSE:
				self->report_action_start (
					SERVICE_PAUSE_PENDING, self->wait_hint.pauseHint());
				self->pause ();
				self->report_action_complete (SERVICE_PAUSED);
				break;
			case SERVICE_CONTROL_CONTINUE:
				self->report_action_start (
					SERVICE_CONTINUE_PENDING, 
					self->wait_hint.resumeHint());
				self->resume ();
				self->report_action_complete (SERVICE_RUNNING);
				break;
			case SERVICE_CONTROL_INTERROGATE:
				if (self->daemon_status.dwCurrentState == SERVICE_RUNNING ||
				    self->daemon_status.dwCurrentState == SERVICE_STOPPED ||
					self->daemon_status.dwCurrentState == SERVICE_PAUSED)
					self->report_action_complete (
						self->daemon_status.dwCurrentState);
				else
					self->report_action_progress ();
				break;
			default: // invalid control code, just ignore it
				;
			}
		}
		
		BOOL Daemon::report_action_complete (DWORD state, DWORD exit_code)
		{
			BOOL n;
		
			daemon_status.dwCurrentState = state;
			daemon_status.dwCheckPoint = 0;
			
			if (exit_code == 0) {
				n = ::SetServiceStatus (status_handle, &daemon_status);
			}
			else {
				daemon_status.dwWin32ExitCode = ERROR_SERVICE_SPECIFIC_ERROR;
				daemon_status.dwServiceSpecificExitCode = exit_code;
				n = ::SetServiceStatus (status_handle, &daemon_status);
				daemon_status.dwWin32ExitCode = NO_ERROR;
				daemon_status.dwServiceSpecificExitCode = NO_ERROR;
			}
			
			return n;
		}
		BOOL Daemon::report_action_start (DWORD state, DWORD hint)
		{
			daemon_status.dwCurrentState = state;
			daemon_status.dwCheckPoint = 0;
			daemon_status.dwWaitHint = hint;
			return ::SetServiceStatus (status_handle, &daemon_status);
		}
		BOOL Daemon::report_action_progress ()
		{
			daemon_status.dwCheckPoint++;
			return ::SetServiceStatus (status_handle, &daemon_status);
		}
		#endif
		
	}
}
